﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Snowdreamist.ComponentModel.Plugin;

namespace Snowdreamist.ComponentModel.Primitives
{
    internal class InstanceImportedPluginTracer : ImportedPluginTracer
    {
        public override IEnumerable<ImportedPlugin<T, TMetadata>> GetPlugins<T, TMetadata>(
            object instance,
            Type ownerType,
            ContractMetadata metadata
            )
        {
            HashSet<ImportedPluginWrapper> pluginWrapperSet;
            if (!this._dict.TryGetValue(instance, out pluginWrapperSet))
                return null;
            lock (pluginWrapperSet)
            {
                return this.FindImportedPlugin<T, TMetadata>(pluginWrapperSet);
            }
        }

        public override void Trace<T, TMetadata>(
            object instance,
            Type ownerType,
            ContractMetadata metadata,
            IEnumerable<ImportedPlugin<T, TMetadata>> importedPlugins
            )
        {
            HashSet<ImportedPluginWrapper> pluginWrapperSet = this._dict.GetOrCreateValue(instance);
            lock (pluginWrapperSet)
            {
                var addingPlugins = importedPlugins.Except(this.FindImportedPlugin<T, TMetadata>(pluginWrapperSet), new ImportedPluginWrapper.Comparer<T, TMetadata>());
                foreach (var plugin in addingPlugins)
                    pluginWrapperSet.Add(new ImportedPluginWrapper(plugin, typeof(T), typeof(TMetadata)));
            }
        }

        #region private

        private readonly ConditionalWeakTable<object, HashSet<ImportedPluginWrapper>> _dict = new ConditionalWeakTable<object, HashSet<ImportedPluginWrapper>>();

        private IEnumerable<ImportedPlugin<T, TMetadata>> FindImportedPlugin<T, TMetadata>(HashSet<ImportedPluginWrapper> wrappers)
        {
            return wrappers.Where(p => p.PluginType == typeof(T) && p.MetadataType == typeof(TMetadata)).Select(p => (ImportedPlugin<T, TMetadata>)p.ImportedPlugin);
        }

        #endregion private
    }

    internal class ImportedPluginWrapper
    {
        public ImportedPluginWrapper(object importedPlugin, Type pluginType, Type metadataType)
        {
            this.ImportedPlugin = importedPlugin;
            this.PluginType = pluginType;
            this.MetadataType = metadataType;
        }

        public object ImportedPlugin { get; private set; }

        public Type PluginType { get; private set; }

        public Type MetadataType { get; private set; }

        public override int GetHashCode()
        {
            return this.PluginType.GetHashCode() + this.MetadataType.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            if (obj is ImportedPluginWrapper)
            {
                ImportedPluginWrapper other = obj as ImportedPluginWrapper;
                return object.ReferenceEquals(this.ImportedPlugin, other.ImportedPlugin);
            }
            else
            {
                return false;
            }
        }

        internal class Comparer<T, TMetadata> : IEqualityComparer<ImportedPlugin<T, TMetadata>>
        {
            public bool Equals(ImportedPlugin<T, TMetadata> x, ImportedPlugin<T, TMetadata> y)
            {
                return x.Equals(y);
            }

            public int GetHashCode(ImportedPlugin<T, TMetadata> obj)
            {
                return obj.GetHashCode();
            }
        }
    }
}